package org.seedframework.sharding.mybatis;

import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.seedframework.sharding.annotations.NonScatter;
import org.seedframework.sharding.annotations.Scatter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;


@Slf4j
public class MapperFactoryScatterBeanAware implements BeanPostProcessor, ApplicationContextAware {


    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        try {
            if (!(bean instanceof MapperFactoryBean)) {
                return bean;
            }
            MapperFactoryBean mapperFactoryBean = (MapperFactoryBean) bean;
            Scatter annotation = (Scatter) mapperFactoryBean.getObjectType().getAnnotation(Scatter.class);
            if (annotation != null) {
                Object strategyObject = registerStrategyBean(annotation.strategy());
                String scatterKey = annotation.key();
                if (!StringUtils.hasText(scatterKey)) {
                    return bean;
                }
                Class mapperInterface = mapperFactoryBean.getMapperInterface();
                Method[] methods = mapperInterface.getMethods();
                String className = mapperInterface.getName();
                for (Method method : methods) {
                    String fullMethodPath = className + "." + method.getName();
                    NonScatter nonScatter = method.getAnnotation(NonScatter.class);
                    if (nonScatter != null) {
                        continue;
                    }

                    // index type and java base type filter
                    // base type named match store
                    Parameter[] parameters = method.getParameters();
                    for (Parameter parameter : parameters) {
                        List<String> routing = new ArrayList<>();
                        if (isPrimitiveOrStringFiledExist(parameter.getType(), parameter.getName(), scatterKey, routing)) {
                            String[] routingArray = new String[routing.size()];
                            for (int i = 0; i < routing.size(); i++) {
                                routingArray[i] = routing.get(i);
                            }
                            ScatterRoutingDefinition routingDefinition = new ScatterRoutingDefinition(scatterKey, strategyObject, routingArray);
                            ScatterRoutingSupport.methodUseScatter(fullMethodPath, routingDefinition);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;

    }


    private Object registerStrategyBean(Class<?> strategyClass) {
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory();
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(strategyClass);
        String className = strategyClass.getName();
        String beanName = className.substring(className.lastIndexOf(".") + 1);
        beanName = beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
        return configurableApplicationContext.getBean(beanName);
    }

    private boolean isPrimitiveOrStringFiledExist(Class target, String typeName, String name, List<String> routing) {
        if (ClassUtils.isPrimitiveOrWrapper(target) || target.getTypeName().equals("java.lang.String")) {
            if (typeName.equals(name)) {
                return true;
            }
        } else {
            routing.add(typeName);
            Field[] fields = target.getDeclaredFields();
            for (Field field : fields) {
                Class c = field.getType();
                return isPrimitiveOrStringFiledExist(c, field.getName(), name, routing);
            }
        }
        return false;
    }
}